package com.flame.auth.server.service;

import com.flame.auth.core.constants.AuthConstants;
import com.flame.auth.core.entity.TrustedPrincipal;
import com.flame.auth.core.event.LoginEvent;
import com.flame.auth.core.exception.AuthExceptionMessage;
import com.flame.auth.core.exception.AuthRuntimeException;
import com.flame.auth.server.controller.vo.LoginVo;
import com.flame.core.utils.CipherUtils;
import com.flame.core.utils.DateUtils;
import com.flame.core.utils.HttpUtils;
import com.flame.parameter.service.ParamsService;
import com.flame.user.constants.AccountConstants;
import com.flame.user.entity.Account;
import com.flame.user.entity.DataAuth;
import com.flame.user.entity.OrgInfo;
import com.flame.user.entity.UserInfo;
import com.flame.user.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * AuthService
 *
 * @author wuxintong
 * @since 2022/12/28
 */
@Service
public class AuthService {

    @Autowired
    private AccountMapper accountMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private ObjAuthMapper objAuthMapper;

    @Autowired
    private DataAuthMapper dataAuthMapper;

    @Autowired
    private OrgInfoMapper orgInfoMapper;

    @Autowired
    private ParamsService paramsService;

    @Autowired
    private ApplicationEventPublisher publisher;

    public TrustedPrincipal login(LoginVo loginVo, HttpServletRequest request) {
        String ip = HttpUtils.getIp(request);
        String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
        String account = loginVo.getAccount();

        Account dbAccount = accountMapper.selectById(account);
        // 1、判断账户存在
        if (dbAccount == null) {
            publisher
                    .publishEvent(new LoginEvent(AuthConstants.LOGIN_FAILURE_ACCOUNT_NOT_EXIST, account, ip, userAgent));
            throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_NOT_EXIST);
        }
        // 2、判断账户状态
        if (AccountConstants.ACCOUNT_STATUS_CANCEL.equals(dbAccount.getAccountStatus())) {
            publisher.publishEvent(
                    new LoginEvent(AuthConstants.LOGIN_FAILURE_ACCOUNT_STATUS_CANCEL, account, ip, userAgent));
            throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_STATUS_ERROR_CANCEL, account);
        }
        if (AccountConstants.ACCOUNT_STATUS_FREEZE.equals(dbAccount.getAccountStatus())) {
            publisher.publishEvent(
                    new LoginEvent(AuthConstants.LOGIN_FAILURE_ACCOUNT_STATUS_FREEZE, account, ip, userAgent));
            throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_STATUS_ERROR_FREEZE, account);
        }
        if (AccountConstants.ACCOUNT_STATUS_LOCK.equals(dbAccount.getAccountStatus())) {
            String diffTime = DateUtils.diff(new Date(), dbAccount.getLockDate());
            if (!"0".equals(diffTime)) {
                publisher.publishEvent(
                        new LoginEvent(AuthConstants.LOGIN_FAILURE_ACCOUNT_STATUS_LOCK, account, ip, userAgent));
                throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_STATUS_ERROR_LOCK, account, diffTime);
            }
            dbAccount.setAccountStatus(AccountConstants.ACCOUNT_STATUS_NORMAL);
            dbAccount.setLoginFailedCount(0);
            accountMapper.updateById(dbAccount);
        }
        try {
            String decryptPassword = CipherUtils.sm2Decrypt(loginVo.getPassword());
            String encryptPassword = CipherUtils.sm3Encrypt(AuthConstants.SALT + decryptPassword + account);
            // 3、校验密码
            if (!dbAccount.getPassword().equals(encryptPassword)) {
                String failedCount = paramsService.getParamsValue("ACCOUNT_LOGIN_FAILED_COUNT");
                if (StringUtils.hasText(failedCount) && !"0".equals(failedCount)) {
                    String lockDuration = paramsService.getParamsValue("ACCOUNT_LOCK_DURATION");
                    if (StringUtils.hasText(lockDuration) && !"0".equals(lockDuration) &&
                            failedCount.equals(String.valueOf(dbAccount.getLoginFailedCount() + 1))) {
                        long duration = Long.parseLong(lockDuration) * 60 * 1000;
                        dbAccount.setAccountStatus(AccountConstants.ACCOUNT_STATUS_LOCK);
                        dbAccount.setLockDate(new Date(System.currentTimeMillis() + duration));
                    }
                    dbAccount.setLoginFailedCount(dbAccount.getLoginFailedCount() + 1);
                    accountMapper.updateById(dbAccount);
                }
                throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_NOT_EXIST);
            }
        } catch (Exception e) {
            publisher.publishEvent(
                    new LoginEvent(AuthConstants.LOGIN_FAILURE_ACCOUNT_NOT_EXIST, account, ip, userAgent));
            throw new AuthRuntimeException(AuthExceptionMessage.ACCOUNT_NOT_EXIST, e);
        }

        // 4、查询角色、权限信息
        UserInfo userInfo = userInfoMapper.findById(account);
        Set<String> roleList = userRoleMapper.listRoleByUserId(account);
        Set<String> authList = new HashSet<>();
        authList.addAll(objAuthMapper.listPermByObjIdAndObjType(account, AuthConstants.OBJ_TYPE_USER));
        if (!CollectionUtils.isEmpty(roleList)) {
            for (String role : roleList) {
                authList.addAll(objAuthMapper.listPermByObjIdAndObjType(role, AuthConstants.OBJ_TYPE_ROLE));
            }
        }

        // 5、查询数据权限信息
        Set<String> userDataAuthSet = new HashSet<>();
        Set<String> orgDataAuthSet = new HashSet<>();
        // 5.1、查询角色数据权限信息
        for (String roleCode : roleList) {
            addDataAuth(userDataAuthSet, orgDataAuthSet, roleCode, AuthConstants.OBJ_TYPE_ROLE);
        }
        // 5.2、查询用户数据权限信息
        addDataAuth(userDataAuthSet, orgDataAuthSet, userInfo.getUserId(), AuthConstants.OBJ_TYPE_USER);
        // 5.3、拼接数据权限信息
        Map<String, String> dataAuthMap = new HashMap<>();
        if (CollectionUtils.isEmpty(userDataAuthSet)) {
            dataAuthMap.put(AuthConstants.DATA_TYPE_USER, " = '" + userInfo.getUserId() + "'");
        } else {
            dataAuthMap.put(AuthConstants.DATA_TYPE_USER, AuthConstants.DATA_TYPE_USER_ALL);
        }
        if (!CollectionUtils.isEmpty(orgDataAuthSet)) {
            String orgCodeList = orgDataAuthSet.stream().map(str -> "'" + str + "'").collect(Collectors.joining(","));
            dataAuthMap.put(AuthConstants.DATA_TYPE_ORG, " in (" + orgCodeList + ")");
        }

        publisher.publishEvent(new LoginEvent(AuthConstants.LOGIN_SUCCESS, account, ip, userAgent));
        // 6、组装 TrustedPrincipal
        TrustedPrincipal trustedPrincipal = new TrustedPrincipal();
        trustedPrincipal.setIp(ip);
        trustedPrincipal.setAccount(userInfo.getUserId());
        trustedPrincipal.setOrgCode(userInfo.getOrgCode());
        trustedPrincipal.setUserName(userInfo.getUserName());
        trustedPrincipal.setRoleList(roleList);
        trustedPrincipal.setAuthList(authList);
        trustedPrincipal.setDataAuthMap(dataAuthMap);
        return trustedPrincipal;
    }

    private void addDataAuth(Set<String> userDataAuthSet, Set<String> orgDataAuthSet, String objCode, String objType) {
        List<DataAuth> dataAuthList = dataAuthMapper.listByObjIdAndObjType(objCode, objType);
        for (DataAuth dataAuth : dataAuthList) {
            if (AuthConstants.DATA_TYPE_USER.equals(dataAuth.getDataType()) &&
                    AuthConstants.DATA_SCOPE_USER_ALL.equals(dataAuth.getDataScope())) {
                userDataAuthSet.add("1");
            }
            if (AuthConstants.DATA_TYPE_ORG.equals(dataAuth.getDataType())) {
                if (AuthConstants.DATA_SCOPE_ORG_ALL.equals(dataAuth.getDataScope())) {
                    orgDataAuthSet.addAll(getSubOrgCodeList(dataAuth.getDataObjCode()));
                }
                orgDataAuthSet.add(dataAuth.getDataObjCode());
            }
        }
    }

    private Set<String> getSubOrgCodeList(String dataObjCode) {
        List<OrgInfo> orgInfoList = orgInfoMapper.listByOrgParent(dataObjCode);
        if (CollectionUtils.isEmpty(orgInfoList)) {
            return new HashSet<>();
        }
        Set<String> orgCodeList = new HashSet<>();
        for (OrgInfo orgInfo : orgInfoList) {
            orgCodeList.add(orgInfo.getOrgCode());
            orgCodeList.addAll(getSubOrgCodeList(orgInfo.getOrgCode()));
        }
        return orgCodeList;
    }
}
